package cn.zhx.domain.strategy.service.armory;

import cn.zhx.domain.strategy.model.entity.StrategyAwardEntity;
import cn.zhx.domain.strategy.model.entity.StrategyEntity;
import cn.zhx.domain.strategy.model.entity.StrategyRuleEntity;
import cn.zhx.domain.strategy.repository.IStrategyRepository;
import cn.zhx.types.common.Constants;
import cn.zhx.types.enums.ResponseCode;
import cn.zhx.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.*;

/**
 * @Author: zhx
 * @Date: 2024/07/02
 * @Description: 策略装配库（兵工厂），负责初始化策略计算
 **/

@Service
@Slf4j
public class StrategyArmoryDispatch implements IStrategyArmory, IStrategyDispatch {

    @Resource
    private IStrategyRepository strategyRepository;


    /**
     * 策略装配 - 装配到redis中
     *
     * @param strategyId
     */
    @Override
    public boolean assembleLotteryStrategy(Long strategyId) {
        //1.查询策略配置  查询所有的奖品
        List<StrategyAwardEntity> strategyAwardEntityList = strategyRepository.queryStragegyAwardList(strategyId);
        assembleLotteryStrategy(String.valueOf(strategyId), strategyAwardEntityList);

        // 缓存奖品库存【用于decr和扣库存使用】
        for(StrategyAwardEntity strategyAward : strategyAwardEntityList){
            Integer awardId = strategyAward.getAwardId();
            Integer awardCount = strategyAward.getAwardCount();
            cacheStrategyAwardCount(strategyId, awardId, awardCount);
        }

        //2.权重策略配置 - 适用于 rule_weight 权重规则配置
        //2.1查询策略实体,判断是否需要权重配置,
        StrategyEntity strategyEntity = strategyRepository.queryStrategyEntityByStrategyId(strategyId);
        String ruleWeight = strategyEntity.getRuleWeight();
        if (null == ruleWeight) return true;

        //2.2查询权重规则实体
        StrategyRuleEntity strategyRuleEntity = strategyRepository.queryStrategyRule(strategyId, ruleWeight);
        if (null == strategyRuleEntity) {
            throw new AppException(ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getCode(), ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getInfo());
        }

        //2.3获取权重值 4000:[102,103,104,105] 5000:[102,103,104,105,106,107] 6000:[102,103,104,105,106,107,108,109]
        /*根据规则权重值映射关系，处理并组装抽奖策略。
        该映射关系由规则ID到其对应的权重值列表组成。通过遍历这个映射，对每个规则，
        只保留与该规则权重值对应的奖项，然后为每个规则生成特定的抽奖策略。*/
        Map<String, List<Integer>> ruleWeightValueMap = strategyRuleEntity.getRuleWeightValues();
        Set<String> keys = ruleWeightValueMap.keySet();
        //遍历所有规则，对每个规则处理其对应的奖项。
        for (String key : keys) {
            //获取当前规则的权重值列表。比如说当前 4000:[102,103,104,105]
            List<Integer> ruleWeightValues = ruleWeightValueMap.get(key);

            //创建策略奖品实体列表的克隆，用于后续筛选出与当前规则相关的奖项。
            //这样做可以保留原列表不变，避免对原始数据的修改。
            ArrayList<StrategyAwardEntity> strategyAwardEntitiesClone = new ArrayList<>(strategyAwardEntityList);

            //筛选出与当前规则权重值对应的奖项，移除不相关的奖项（不在权重值列表中）。
            strategyAwardEntitiesClone.removeIf(entity -> !ruleWeightValues.contains(entity.getAwardId()));

            //组装并处理特定规则的抽奖策略。这里通过拼接规则ID和当前遍历的规则键，生成了唯一的策略标识符。
            assembleLotteryStrategy(String.valueOf(strategyId).concat(Constants.UNDERLINE).concat(key), strategyAwardEntitiesClone);
        }

        return true;
    }

    @Override
    public void assembleLotteryStrategyByActivityId(Long activityId) {
        Long strategyId = strategyRepository.queryStrategyIdByActivityId(activityId);
        assembleLotteryStrategy(strategyId);
    }

    //缓存奖品库存
    private void cacheStrategyAwardCount(Long strategyId, Integer awardId, Integer awardCount) {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
        strategyRepository.cacheStrategyAwardCount(cacheKey, awardCount);
    }

    //3种奖品 中奖率分别为0.1，0.4，0.5。
    //十分位，10 * 0.1 = 1个槽 10 * 0.4 = 4个槽 10 * 0.5 = 5个槽
    public void assembleLotteryStrategy(String key, List<StrategyAwardEntity> strategyAwardEntityList) {
        //2.获取最小概率值 0.0001
        BigDecimal minAwardRate = strategyAwardEntityList.stream()
                .map(StrategyAwardEntity::getAwardRate)
                .min(BigDecimal::compareTo)
                .orElse(BigDecimal.ZERO);

        //3. 用 1 * 1000 获得概率范围，百分位、千分位、万分位 0.0001 -> 10000
        BigDecimal rateRange = BigDecimal.valueOf(convert(minAwardRate.doubleValue()));

        //4. 遍历策略配置，计算出每个概率值需要存放到查找表的数量，循环填充 | 0.3概率 10000个槽  0.3 * 10000 = 3000个
        ArrayList<Integer> strategyAwardsSearchRateTables = new ArrayList<>(rateRange.intValue());
        for (StrategyAwardEntity strategyAwardEntity : strategyAwardEntityList) {
            Integer awardId = strategyAwardEntity.getAwardId();
            BigDecimal awardRate = strategyAwardEntity.getAwardRate();

            //计算出每个概率值需要存放到查找表的数量，循环填充
            for (int i = 0; i < rateRange.multiply(awardRate).intValue(); i++) {
                strategyAwardsSearchRateTables.add(awardId);
            }
        }

        //5.乱序
        Collections.shuffle(strategyAwardsSearchRateTables);

        //6.存储到hashMap中，value是奖品id
        HashMap<Integer, Integer> shuffleStrategyAwardSearchRateTables = new HashMap<>();
        for (int i = 0; i < strategyAwardsSearchRateTables.size(); i++) {
            shuffleStrategyAwardSearchRateTables.put(i, strategyAwardsSearchRateTables.get(i));
        }

        //7.存储到redis
        strategyRepository.storeStrategyAwardSearchRateTables(key, BigDecimal.valueOf(shuffleStrategyAwardSearchRateTables.size()), shuffleStrategyAwardSearchRateTables);
    }

    /**
     * 获取随机奖品
     */
    @Override
    public Integer getRandomAwardId(Long strategyId) {
        int rateRange = strategyRepository.getRateRange(strategyId);
        return strategyRepository.getStrategyAwardAssemble(String.valueOf(strategyId), new SecureRandom().nextInt(rateRange));
    }

    /**
     * 获取随机奖品 根据规则权重值
     * @param strategyId 策略id
     * @param ruleWeightValue 权重值
     * @return
     */
    @Override
    public Integer getRandomAwardId(Long strategyId, String ruleWeightValue) {
        String key = String.valueOf(strategyId).concat("_").concat(ruleWeightValue);
        int rateRange = strategyRepository.getRateRange(key);
        return strategyRepository.getStrategyAwardAssemble(key, new SecureRandom().nextInt(rateRange));
    }

    @Override
    public Boolean subtractAwardCount(Long strategyId, Integer awardId, Date endDateTime) {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
        return strategyRepository.subtractionAwardStock(cacheKey,strategyId,awardId,endDateTime);
    }

    /**
     * 转换计算，只根据小数位来计算。如【0.01返回100】、【0.009返回1000】、【0.0018返回10000】
     */
    private double convert(double min) {
        double current = min;
        double max = 1;
        while (current < 1) {
            current = current * 10;
            max = max * 10;
        }
        return max;
    }

}
